package org.hawk.gwt.ppc.reflect;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import junit.framework.TestCase;

import org.hawk.gwt.ppc.javacc.JavaParserTreeConstants;
import org.hawk.gwt.ppc.javacc.JavaScanner;
import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.reflect.mock.Mock1;
import org.hawk.gwt.ppc.reflect.mock.Mock1.MockInner;
import org.hawk.gwt.ppc.reflect.mock.Mock1.MockInner2;
import org.hawk.gwt.ppc.reflect.mock.Mock2;
import org.hawk.gwt.ppc.reflect.mock.Mock3;

public class MetadataTestCase extends TestCase {
	private static final String MOCK01 = "./src/org/hawk/gwt/ppc/reflect/mock/Mock1.java";
	private static final String MOCK02 = "./src/org/hawk/gwt/ppc/reflect/mock/Mock2.java";
	private static final String MOCK03 = "./src/org/hawk/gwt/ppc/reflect/mock/Mock3.java";
	
	public void testClasses() throws FileNotFoundException, ParseException{
		MetadataProcessor processor = scan();
		
		assertNotNull(processor.getClasses());
		assertEquals(5, processor.getClasses().size());
		assertEquals(Arrays.asList(new String[]{
				Mock1.class.getName(),
				MockInner.class.getName().replace('$', '.'),
				MockInner2.class.getName().replace('$', '.'),
				Mock2.class.getName(),
				Mock3.class.getName()	
		}),	processor.getClasses());
		assertEquals(Arrays.asList(new String[]{
				Mock1.class.getName(),
				Mock2.class.getName(),
				Mock3.class.getName()	
		}),	processor.getTopClasses());
		assertModifiers(processor.getModifier(Mock1.class.getName(),""),
				JavaModifier.PUBLIC);
	}
	
	public void testFields() throws IOException, ParseException{
		MetadataProcessor processor = scan();
		
		assertEquals(Arrays.asList(new String[]{
				"long id","java.lang.String name","java.util.Map map"}),
			processor.getDeclaredFields(Mock1.class.getName()));
		assertModifiers(processor.getModifier(Mock1.class.getName(),"id"),
				JavaModifier.PRIVATE);
	}
	
	public void testConstructors() throws IOException, ParseException{
		MetadataProcessor processor = scan();
		assertEquals(Collections.emptyList(),
			processor.getDeclaredConstructors(Mock1.class.getName()));
		assertEquals(Arrays.asList(new String[]{"Mock3()",
				"Mock3(java.lang.String)","Mock3(org.hawk.gwt.ppc.reflect.mock.Mock3)",
				"Mock3(java.lang.String,org.hawk.gwt.ppc.reflect.mock.Mock3)"}),
			processor.getDeclaredConstructors(Mock3.class.getName()));
		
		assertModifiers(processor.getModifier(Mock3.class.getName(),"Mock3()"),
				JavaModifier.PUBLIC);
		assertModifiers(processor.getModifier(Mock3.class.getName(),
				"Mock3(java.lang.String)"),JavaModifier.DEFAULT);
		assertModifiers(processor.getModifier(Mock3.class.getName(),
				"Mock3(org.hawk.gwt.ppc.reflect.mock.Mock3)"),
				JavaModifier.PROTECTED);
		assertModifiers(processor.getModifier(Mock3.class.getName(),
				"Mock3(java.lang.String,org.hawk.gwt.ppc.reflect.mock.Mock3)"),
				JavaModifier.PRIVATE);
	}

	public void testMethods() throws IOException, ParseException{
		MetadataProcessor processor = scan();
		assertEquals(Arrays.asList(new String[]{
			"void method01()","int method02(int)",
			"org.hawk.gwt.ppc.reflect.mock.Mock1 method03(java.lang.String)"}),
			processor.getDeclaredMethods(Mock1.class.getName()));
		assertModifiers(processor.getModifier(Mock1.class.getName(),
			"method01()"),JavaModifier.PUBLIC);
		assertModifiers(processor.getModifier(Mock1.class.getName(),
			"method02(int)"),JavaModifier.PUBLIC);
		assertModifiers(processor.getModifier(Mock1.class.getName(),
			"method03(java.lang.String)"),JavaModifier.PRIVATE);
	}
	
	public void testInheritance() throws IOException, ParseException{
		MetadataProcessor processor = scan();
		assertEquals(Arrays.asList(new String[]{
			"com.google.gwt.core.client.EntryPoint","java.io.Serializable"}),
				processor.getImplementedInterfaces(Mock1.class.getName()));
		assertEquals(Object.class.getName(),
				processor.getExtendedClass(Mock1.class.getName()));
		assertEquals(Mock1.class.getName(),
				processor.getExtendedClass(Mock3.class.getName()));
	}
	
	public void testImports() throws IOException, ParseException{
		MetadataProcessor processor = scan();
		assertEquals(new HashSet<String>(Arrays.asList(
				new String[]{"java.io.Serializable",
				"java.util.Map","com.google.gwt.core.client.EntryPoint"})),
			new HashSet<String>(processor.getImports(Mock1.class.getName())));
	}

	private MetadataProcessor scan() throws ParseException,
			FileNotFoundException {
		StringWriter stream = new StringWriter();
		MetadataProcessor processor = new MetadataProcessor();
		JavaScanner scanner = new JavaScanner();
		scanner.addProcessor(JavaParserTreeConstants.JJTCOMPILATIONUNIT,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCLASSORINTERFACEBODY,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTEXTENDSLIST,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTPACKAGEDECLARATION,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPLEMENTSLIST,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTFIELDDECLARATION,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTMETHODDECLARATOR,
				processor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPORTDECLARATION,
				processor);
		
		scanner.startScan(new FileReader(MOCK01),stream);
		scanner.startScan(new FileReader(MOCK02),stream);
		scanner.startScan(new FileReader(MOCK03),stream);
		return processor;
	}
	
	private void assertModifiers(List<JavaModifier> list,JavaModifier...expected){
		if(list==null||expected.length!=list.size()){
			throw new AssertionError("Expected "+expected.length+
					" elements but was "+(list==null?"NULL":String.valueOf(list.size())));
		}
		for(int i=0;i<list.size();i++){
			if(!expected[i].equals(list.get(i))){
				throw new AssertionError("Expected "+
					expected[i]+" but was "+list.get(i)+" at position "+i);
			}
		}
	}
}
